
package com.x52im.rbchat.logic.chat_root;

import android.app.Activity;
import android.content.Intent;
import android.graphics.drawable.AnimationDrawable;
import android.text.SpannableString;
import android.text.style.DynamicDrawableSpan;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;

import com.bumptech.glide.Glide;
import com.eva.android.ArrayListObservable;
import com.eva.android.widget.AListAdapter2;
import com.eva.android.widget.AsyncBitmapLoader;
import com.eva.android.widget.WidgetUtils;
import com.eva.android.widget.alert.AlertDialog;
import com.eva.epc.common.util.CommonUtils;
import com.x52im.rainbowchat.im.dto.ChatType;
import com.x52im.rbchat.MyApplication;
import com.x52im.rbchat.R;
import com.x52im.rbchat.cache.ImageCacheLoader;
import com.x52im.rainbowchat.http.logic.dto.RosterElementEntity;
import com.x52im.rainbowchat.im.dto.MsgType;
import com.x52im.rainbowchat.im.dto.RevokedMeta;
import com.x52im.rbchat.logic.alarm.AlarmType;
import com.x52im.rbchat.logic.chat_root.face.EmojiUtil;
import com.x52im.rbchat.logic.chat_root.impl.VoicePlayerWrapper;
import com.x52im.rbchat.logic.chat_root.model.Message;
import com.x52im.rbchat.logic.chat_root.model.Message.DownloadStatus;
import com.x52im.rbchat.logic.chat_root.model.Message.SendStatus;
import com.x52im.rbchat.logic.chat_root.model.ContactMeta;
import com.x52im.rbchat.logic.chat_root.model.FileMeta;
import com.x52im.rbchat.logic.chat_root.model.LocationMeta;
import com.x52im.rbchat.logic.chat_root.model.MessageExt;
import com.x52im.rbchat.logic.chat_root.model.VoipRecordMeta;
import com.x52im.rbchat.logic.chat_root.sendfile.ReceivedFileHelper;
import com.x52im.rbchat.logic.chat_root.sendimg.SendImageHelper;
import com.x52im.rbchat.logic.chat_root.sendshortvideo.ReceivedShortVideoHelper;
import com.x52im.rbchat.logic.more.UserActivity;
import com.x52im.rbchat.logic.qrcode.utils.QRCodeScheme;
import com.x52im.rbchat.logic.sns_group.JoinGroupActivity;
import com.x52im.rbchat.network.http.async.QueryFriendInfo;
import com.x52im.rbchat.utils.IntentFactory;

import java.io.File;
import java.util.ArrayList;
import java.util.Observer;

/**
 * 聊天界面的消息列表adapter根类.
 *
 * <p>
 * 此类是基于一对一好友聊天进行设计，当子类在使用时：一对一好友聊天时基本可以直接使用、陌生人和
 * 群聊等其它类型聊天时可Override相应的方法从而实现其不同于一对一好友聊天的行为。
 * </p>
 * 
 * @author Jack Jiang(http://www.52im.net/space-uid-1.html)
 * @version 1.0
 */
public abstract class AbstractChattingListAdapter extends AListAdapter2<Message> {

	private final static String TAG = AbstractChattingListAdapter.class.getSimpleName();
	
	/** 本adapter对应的ListView对象引用 */
	protected ListView theListView = null;
	/** 最后一条是否真处于可见状态（通常是ListView是已被滑到了底部） */
	private boolean lastItemVisible = true;
	
//	/**
//	 * 对方的uid（目前此字段仅用于点击人员头像查看该人信息时）.
//	 * 此字段对于BBS聊天是无意义的，因为它不是一对一哦！ */
//	protected String friendUID = null;

	/** 对应聊天界面中的聊天类型。 @see {@link ChatType} */
	protected int theChatType = -1;
	/** 对应聊天界面中的聊天id。一对一聊天时是存放的是对方的uid，群聊时是群id */
	protected String theToId = null;
	
	/** 图片的缓存类，提升性能 */
	protected AsyncBitmapLoader asyncLoader = null;

    private VoicePlayerWrapper voicePlayerWrapper = null;
	/** 当前语音留言播放中的实体对象句柄 */
	private Message _currentVoicePlayingCME = null;
	
	/** @see ContentOnClickListener */
	private boolean needDump = false;//true
	
	public AbstractChattingListAdapter(Activity context, ListView theListView, int chatType, String toId, boolean needDump) {

		super(context, -1);
		
		this.theListView = theListView;
		this.theChatType = chatType;
		this.theToId = toId;
		this.needDump = needDump;
		this.asyncLoader = new AsyncBitmapLoader(SendImageHelper.getSendPicSavedDirHasSlash());
        this.voicePlayerWrapper = new VoicePlayerWrapper(context, this) {
			@Override
			protected Message getCurrentVoicePlayingCME() {
				return AbstractChattingListAdapter.this._currentVoicePlayingCME;
			}

			@Override
			protected void setCurrentVoicePlayingCME(Message currentVoicePlayingCME) {
				AbstractChattingListAdapter.this._currentVoicePlayingCME = currentVoicePlayingCME;
			}
		};
		
		// 为关联的ListView添加滑动事件处理
		if(theListView != null) {
			theListView.setOnScrollListener(new OnScrollListener() {
				@Override
				public void onScrollStateChanged(AbsListView view, int scrollState) {
				}

				@Override
				public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
					if(visibleItemCount+firstVisibleItem==totalItemCount) {
//						Log.e("SSSSSSSS", "【B-列表】最后一行可见了.（visibleItemCount="+visibleItemCount+",firstVisibleItem="+firstVisibleItem+",totalItemCount="+totalItemCount+"）");
						setLastItemVisible(true);
						onScrollToBottom();
					}
					else {
//                      Log.e("SSSSSSSS", "【C-列表】最后一行不可见了！（visibleItemCount="+visibleItemCount+",firstVisibleItem="+firstVisibleItem+",totalItemCount="+totalItemCount+"）");
                        setLastItemVisible(false);
                    }
				}
			});
		}
	}
	
	public boolean isLastItemVisible() {
		return lastItemVisible;
	}

	private void setLastItemVisible(boolean lastItemVisible) {
		this.lastItemVisible = lastItemVisible;
	}
	
	/**
	 * 当列表滑到最后一行时将调用的方法.
	 * <p>
	 * 默认本方法什么也不做
	 */
	protected void onScrollToBottom() {
		// 默认本方法什么也不做
	}

	@Override 
	public int getItemViewType(int position) {
		Message entity = getChattingDatas().get(position);
		return ChattingListItemTypeManager.getInstance().getItemViewType(entity.getMsgType(), entity.isOutgoing());
	}
	@Override
	public int getViewTypeCount() {
		return ChattingListItemTypeManager.getInstance().getViewTypeCount();
	}

	//重写本方法，以便使用自定义的列表数据模型：使用全局静态变量，目的是无论Ui如何变化都保留上次的查询结果，
	//以方便用户查看，提高用户体验，这对查询类的功能比较有用哦。另注：AListAdapter里的列表数据模型列象名就是listData，
	//所以本类里要使用自已的对象则再也不能用listData哦，否则本方法里将错误地引用于AListAdapter里的listData
	//从而导致空指针异常（之前就是碰到了这样的问题，让人百思不得其解）.
	@Override//本方法只在父类AListAdapter的构造方法里被调用一次，所以一定要注意staticListData被实例化的时机
	protected ArrayList<Message> createListData() {
		//注意：不能使用与AListAdapter里默认的数据模型listData一样的名字哦，否则将错误地引用父类该对象从而导致空指针
		return getChattingDatas().getDataList();
	}

	@Override
	public View getView(final int position, View convertView, ViewGroup parent) {
		Message entity = getChattingDatas().get(position);
		int msgType = entity.getMsgType();
		boolean isOutgoing = entity.isOutgoing();

		ChattingListViewHolder viewHolder = null;
		if (convertView == null) {
			convertView = AbstractChattingListAdapterExt.inflateChattingListItemView(layoutInflater, msgType, isOutgoing);
			if(convertView == null)
			    return null;

			// 服务端发过来的系统通知不需要这些额外的UI设置和处理，跟普通聊天消息分开
			if(msgType == MsgType.TYPE_SYSTEAM$INFO) {
				viewHolder = new ChattingListViewHolder();
				viewHolder.mvSendTime_v2020 = convertView.findViewById(R.id.mv_sendtime2020);
				viewHolder.mvContent = (TextView) convertView.findViewById(R.id.mv_chatcontent);
			}
			// 被撤回消息不需要这些额外的UI设置和处理，跟普通聊天消息分开
			else if(msgType == MsgType.TYPE_REVOKE) {
				viewHolder = new ChattingListViewHolder();
				viewHolder.mvSendTime_v2020 = convertView.findViewById(R.id.mv_sendtime2020);
				viewHolder.mvContent = (TextView) convertView.findViewById(R.id.mv_chatcontent);
			}
			// 通聊天消息的处理
			else {
				viewHolder = new ChattingListViewHolder();
				viewHolder.isComMsg = msgType;


				/* ------------------------ 【聊天消息气泡中的组件命名约定】 --------------------------- */
				/* 约定1：所有聊天消息通用(必须)的组件，命名以"mv"开头、非通用组件以"ev"开头                   */
				/* 约定2：当通用组件("mv"开头)对于该类消息无意义时，可在layout中设置gone（不可见），但不可没有 */
				/* ---------------------------------------------------------------------------------- */


				/** ------------ 通用组件（通用组件命名"mv"开头、非通用组件"ev"开头） START ------------------- */
				viewHolder.mvSendTime_v2020 = convertView.findViewById(R.id.mv_sendtime2020);
				viewHolder.mvContentMainLayout = convertView.findViewById(R.id.mv_chatcontent_main_layout);

				viewHolder.mvContent = convertView.findViewById(R.id.mv_chatcontent);
				viewHolder.mvContentIcon = convertView.findViewById(R.id.mv_chatcontent_icon);
				viewHolder.mvContentDesc = convertView.findViewById(R.id.mv_chatcontent_desc);

				viewHolder.mvChatSendFaild = convertView.findViewById(R.id.mv_chat_send_faild);
				viewHolder.mvUserHead = convertView.findViewById(R.id.mv_userhead);
				viewHolder.mvUserName = convertView.findViewById(R.id.mv_username);
				/** ------------ 通用组件 END ------------------------------------------------------------- */

				/** ------------ 发出/收到的文本消息专用组件 START ------------------- */
				viewHolder.evQuoteLayout = convertView.findViewById(R.id.chatting_list_item_quote_ll);
				viewHolder.evQuoteContentTypeIcon = convertView.findViewById(R.id.mv_chatcontent_type_icon_forQuote);
				viewHolder.evQuoteContentIconLayout = convertView.findViewById(R.id.mv_chatcontent_icon_layout_forQuote);
				/** ------------ 发出/收到的文本消息专用组件 END ------------------- */

				/** ------------ 发出的图片消息、发出的语音消息、发出的大文件消息、发出的短视频消息专用组件 START ------------------- */
				// for secondary: 当前只针对发送图片方
				viewHolder.evSendstatusSencondaryLayout = convertView.findViewById(R.id.ev_sendstatus_sencondary_ll);
				viewHolder.evSendstatusSencondaryHintView = convertView.findViewById(R.id.ev_sendstatus_sencondary_viewHint);
				/** ------------ 发出的图片消息、发出的语音消息、发出的大文件消息、发出的短视频消息专用组件 END --------------------- */

				/** ------------ 发出/收到的礼品消息专用组件 START --------------------- */
				// 用于赠送（包括发出的、收到的）的礼品消息时的背景效果
				viewHolder.evGiftRotateEfectView = convertView.findViewById(R.id.ev_chatcontent_gift_rotate_efectView);
				/** ------------ 发出/收到的礼品消息专用组件 END ------------------------ */

				/** ------------ 发出/收到的名片消息专用组件 START ---------------------- */
				// 名片消息：仅用于名片消息（包括收到的和发出的）
				viewHolder.ev_chatcontent_type_forContact = convertView.findViewById(R.id.ev_chatcontent_type_forContact);
				/** ------------ 发出/收到的名片消息专用组件 END ------------------------- */

				/** ------------ 收到的语音留言消息、发出的大文件消息、发出的短视频消息专用组件 START --------------------- */
				// 下载状态显示组件：当前只针对收到的语音留言方
				viewHolder.evProgressBar = convertView.findViewById(R.id.ev_chatcontent_Progress);
				/** ------------ 收到的语音留言消息、发出的大文件消息、发出的短视频消息专用组件 START --------------------- */


				// ** 增加事件监听处理
				// 消息内容点击事件处理
				ContentOnClickListener contentOnClickListener = new ContentOnClickListener(false, needDump);
				viewHolder.mvContentMainLayout.setOnClickListener(contentOnClickListener);
				viewHolder.contentOnClickListener = contentOnClickListener;
				// 被引用消息内容点击事件处理
				if(viewHolder.evQuoteLayout != null) {
					ContentOnClickListener quoteContentOnClickListener = new ContentOnClickListener(true, needDump);
					viewHolder.evQuoteLayout.setOnClickListener(quoteContentOnClickListener);
					viewHolder.quoteContentOnClickListener = quoteContentOnClickListener;
				}
				// 用户头像点击事件
				HeadIconOnClickListener headIconOnClickListener = new HeadIconOnClickListener();
				viewHolder.mvUserHead.setOnClickListener(headIconOnClickListener);
				viewHolder.headIconOnClickListener = headIconOnClickListener;
				// 发送失败后的失败提示图标点击事件
				if(viewHolder.mvChatSendFaild != null)
				{
                    SendFailedImageOnClickListener sendFailedImageOnClickListener = new SendFailedImageOnClickListener();
                    viewHolder.mvChatSendFaild.setOnClickListener(sendFailedImageOnClickListener);
                    viewHolder.sendFialedOnClickListener = sendFailedImageOnClickListener;
                }
			}

			convertView.setTag(viewHolder);
		}
		else
			viewHolder = (ChattingListViewHolder) convertView.getTag();

		// 系统通知的显示和处理（服务端发过来的系统通知不需要这些额外的UI设置和处理，所以跟普通聊天消息分开）
		if (msgType == MsgType.TYPE_SYSTEAM$INFO) {
			viewHolder.mvSendTime_v2020.setText(entity.getDateHuman());
			viewHolder.mvSendTime_v2020.setVisibility(entity.isShowTopTime() ? View.VISIBLE : View.GONE);
			((TextView) viewHolder.mvContent).setText(entity.getText());
		}
		// 被撤回消息的内容显示（被撤回消息不需要这些额外的UI设置和处理，所以跟普通聊天消息分开）
		else if (msgType == MsgType.TYPE_REVOKE) {
			viewHolder.mvSendTime_v2020.setText(entity.getDateHuman());
			viewHolder.mvSendTime_v2020.setVisibility(entity.isShowTopTime() ? View.VISIBLE : View.GONE);
			((TextView) viewHolder.mvContent).setText(MessageExt.getMessageContentPreviewForRevoked(context, RevokedMeta.fromJSON(entity.getText())));
		}
		// 普通聊天消息的显示和处理
		else {
			//** 确保每次item被刷新时都更新操作组件事件监听器中要记录的该item所对应的 列表的行索引号
			//** 这样就能很好地确保行索引号的正确性了
			ContentOnClickListener contentOnClickListener = (ContentOnClickListener) viewHolder.contentOnClickListener;
			contentOnClickListener.setPosition(position);
			// 被引用消息内容点击事件处理
			if(viewHolder.evQuoteLayout != null) {
				ContentOnClickListener quoteContentOnClickListener = (ContentOnClickListener) viewHolder.quoteContentOnClickListener;
				quoteContentOnClickListener.setPosition(position);
			}
			HeadIconOnClickListener headIconOnClickListener = (HeadIconOnClickListener) viewHolder.headIconOnClickListener;
			headIconOnClickListener.setPosition(position);
			if(viewHolder.mvChatSendFaild != null)
			{
                SendFailedImageOnClickListener sendFailedImageOnClickListener = (SendFailedImageOnClickListener) viewHolder.sendFialedOnClickListener;
                sendFailedImageOnClickListener.setPosition(position);
            }

			/** ► 消息时间、用户昵称的显示等 */
			// 消息时间
			viewHolder.mvSendTime_v2020.setText(entity.getDateHuman());
			viewHolder.mvSendTime_v2020.setVisibility(entity.isShowTopTime()?View.VISIBLE:View.GONE);
			// 用户昵称
			if (entity.isOutgoing()) { // 默认本地用户无条件不显示昵称
				viewHolder.mvUserName.setVisibility(View.GONE);
			} else {
				if (isShowNickName() && !CommonUtils.isStringEmpty(entity.getSenderDisplayName(), true)) {
					viewHolder.mvUserName.setVisibility(View.VISIBLE);
					viewHolder.mvUserName.setText(entity.getSenderDisplayName());
				} else
					viewHolder.mvUserName.setVisibility(View.GONE);
			}

			/** ► 发送状态的显示（-> 针对所有聊天消息）*/
			setupSendStatusForGetView(entity, viewHolder.mvChatSendFaild);

			/** ► 播放状态的显示（-> 针对语音聊天消息(包括收到的、发出的)）*/
			setupPlayStatusForVoiceView(entity, viewHolder.mvContentIcon);

			/** ► 下载状态的显示（-> 针对语音聊天消息(仅针对收到的)）*/
			setupDownloadStatusForVoiceView(entity, viewHolder.evProgressBar);

			/** ► 消息内容的显示（-> 针对所有聊天消息）*/
			AbstractChattingListAdapterExt.setChatMessageItemContentValue(context
					, this
					, viewHolder
					, this.theChatType
					, this.theToId
					, entity.getText()      // 注意：此消息内容可能并非扁平文本，以不同消息定义的封装对象为准(即它可能是JSON文本)
					, entity.getMsgType()
//					, entity.isShortVideoMsg()? asyncLoaderForShortVideo:asyncLoader
					, this.asyncLoader
					, entity.isOutgoing()
					, entity
			);

			/** ► 上传处理及状态显示（-> 针对图片和语音留言消息的文件）*/
			if (isOutgoing && (entity.getMsgType() == MsgType.TYPE_IMAGE || entity.getMsgType() == MsgType.TYPE_VOICE)) {
				AbstractChattingListAdapterExt.processImageOrVoiceMessageOfSentForGetView(context
						, entity, viewHolder.evSendstatusSencondaryLayout, viewHolder.evSendstatusSencondaryHintView
						, createObserverForImageOrVoiceUploadProcessOK(entity.getMsgType()), entity.getMsgType());
			}

			/** ► 上传处理及状态显示（-> 针对大文件消息的文件） */
			if (isOutgoing && entity.getMsgType() == MsgType.TYPE_FILE) {
				AbstractChattingListAdapterExt.processBigFileMessageOfSentForGetView(entity, viewHolder);
			}

			/** ► 上传处理及状态显示（-> 针对短视频消息的文件）*/
            if (isOutgoing && entity.getMsgType() == MsgType.TYPE_SHORTVIDEO) {
				AbstractChattingListAdapterExt.processShortVideoMessageOfSentForGetView(entity, viewHolder);
            }

			/** ► 用户头像的显示（-> 针对所有聊天消息）*/
			setupHeadIconForGetView(entity, viewHolder.mvUserHead);

//			/** ► 户昵称ui的可见性（-> 针对所有聊天消息）*/
//			setupUserNickNameVisible(entity.getMsgType(), viewHolder.tvUserName);

			/** ► 礼品背景动画效果的显示（-> 针对赠送的礼品）*/
			if (!isOutgoing && entity.getMsgType() == MsgType.TYPE_GIFT$SEND)
				((AnimationDrawable) (viewHolder.evGiftRotateEfectView.getBackground())).start();

			/** ► 高亮显示一次（仅在由搜索功能进入聊天界面时，用于1次高亮显示该条搜索到消息时使用） */
			if (entity.isHighlightOnce()) {
				WidgetUtils.highlightOnceMessageItem(entity, convertView);
			}
		}

		return convertView;
	}
	
	/**
	 * ► 播放状态的显示（-> 针对语音聊天消息(包括收到的、发出的)）.
	 * 
	 * @param entity
	 * @param viewPlayStatus
	 */
	protected void setupPlayStatusForVoiceView(Message entity, ImageView viewPlayStatus) {
		boolean isOutgoing = entity.isOutgoing();

		// 语音留言消息
		if (entity.getMsgType() == MsgType.TYPE_VOICE) {
			// 如果当前单元正是“播放中”状态（this.currentVoicePlayingCME == entity当然就意味着是播放中罗）
			if (voicePlayerWrapper.isEntityVoicePlaying(entity)) {
				int playAnim_resId = 0;
				if (isOutgoing)
					playAnim_resId = R.drawable.chatting_voice_playing_anim_r;// 逐帧动画不能用Animation.loadAnimation！！否则报“Unknown animation name: animation-list”
				else
					playAnim_resId = R.drawable.chatting_voice_playing_anim_l;// 逐帧动画不能用Animation.loadAnimation！！否则报“Unknown animation name: animation-list”

				// 开始“播放中”动画的显示
				if (playAnim_resId != 0) {
					viewPlayStatus.setImageResource(playAnim_resId);
					((AnimationDrawable) viewPlayStatus.getDrawable()).start();
				}
			}
			// 否则不是处于“播放中”，则尝试恢复它的ui显示为正常状态即可
			else {
				int normal_resId = 0;
				if (isOutgoing)
					normal_resId = R.drawable.chatting_voice_normal_r;
				else
					normal_resId = R.drawable.chatting_voice_normal_l;

				viewPlayStatus.setImageResource(normal_resId);
			}
		}
	}
	
	/**
	 * ► 下载状态的显示（-> 针对语音聊天消息(仅针对收到的)）.
	 * 
	 * @param entity
	 * @param viewProgressBar
	 */
	protected void setupDownloadStatusForVoiceView(Message entity, ProgressBar viewProgressBar) {
		// 收到的语音留言消息
		if(!entity.isOutgoing() && entity.getMsgType() == MsgType.TYPE_VOICE) {
			int progressStatus = entity.getDownloadStatus().getStatus();
			switch(progressStatus) {
				case DownloadStatus.NONE:
				case DownloadStatus.PROCESS_OK:
				case DownloadStatus.PROCESS_FAILD:
					viewProgressBar.setVisibility(View.GONE);
					break;
				case DownloadStatus.PROCESSING:
					// 设置进度条可见
					viewProgressBar.setVisibility(View.VISIBLE);
					// 刷新进度值显示
					viewProgressBar.setProgress(entity.getDownloadStatus().getProgress());
					break;
			}
		}
	}
	
	/**
	 * 图片、语音上传处理成功后要通知的观察者.
	 * <p>
	 * 此观察者使用时，调用者会通过update方法传递一个Object[]的2元数组.
	 * 
	 * @param msgType MsgType
	 * @return
	 */
	protected Observer createObserverForImageOrVoiceUploadProcessOK(final int msgType) {
		return (observable, data) -> {
			Object[] dateFromSrc = (Object[])data;
			String imageOrVoiceFileName = (String)dateFromSrc[0];
			String fingerPring = (String)dateFromSrc[1];

			// 图片消息的图片上传到服务端（用于暂存和中转）成功后即发送一条消息给聊天的对方
			sendImageOrVoiceMessageAsyncImpl(msgType, true, imageOrVoiceFileName, fingerPring);
		};
	}
	/**
	 * 图片或语音留言消息的图片上传到服务端（用于暂存和中转）成功后即发送一条消息给聊天的对方.
	 * 
	 * @param msgType MsgType
	 * @param imageOrVoiceFileName
	 * @param fingerPring
	 */
	protected abstract void sendImageOrVoiceMessageAsyncImpl(int msgType, boolean isOutgoing, String imageOrVoiceFileName, String fingerPring);
	
	/**
	 * ► 发送状态的显示（-> 针对所有聊天消息）.
	 * 
	 * @param entity
	 * @param viewSendFaild
	 */
	protected void setupSendStatusForGetView(Message entity, ImageView viewSendFaild) {
		// 文本消息
		if (entity.isOutgoing()) {
			// 处理发送状态的ui展现
			int ss = entity.getSendStatus();
			if (ss == SendStatus.sendFaild) {
				viewSendFaild.setImageResource(R.drawable.chatting_list_view_item_msg_send_error);
				viewSendFaild.setVisibility(View.VISIBLE);
			} else if (ss == SendStatus.beReceived) {
				viewSendFaild.setImageDrawable(null);
				viewSendFaild.setVisibility(View.GONE);
			}
			// sending
			else {
				viewSendFaild.setImageResource(R.drawable.common_loading_small1);
				viewSendFaild.setVisibility(View.VISIBLE);
			}

			// 启动sending状态下的滚动条显示
			if (viewSendFaild.getDrawable() != null && viewSendFaild.getDrawable() instanceof AnimationDrawable) {
				AnimationDrawable ad = (AnimationDrawable) (viewSendFaild.getDrawable());
				ad.start();
			}
		}
	}
	
	/**
	 * 设置消息头像的显示.
	 * 
	 * @param entity
	 * @param viewHead
	 */
	protected void setupHeadIconForGetView(Message entity, ImageView viewHead)
	{
		if(viewHead == null)
			return;

		// 通过clear()解决图片加载错乱问题，clear更好，因为它不会在发生图片显示时闪烁一下的问题
		Glide.with(context).clear(viewHead);// 解决方法2
		// clear完成后设置默认占位图，确保图片显示不出现Glide导致的错位问题
		viewHead.setImageResource(R.drawable.default_avatar_for_chattingui_40_3x);

		// 消息发送者的uid
		String uid = entity.getSenderId();
		// 用户的头像文件名（这个文件名目前仅用于Glide缓存图片时作为signature字段使用）
		String avatarFileName = null;
		// 不从sd卡读取缓存（true将确保app启动后首次加载时能从网络加载一次）
		boolean dontDiskCache = false;

		// 本地用户发出的消息
        if(entity.isOutgoing()) {
			// 读取本地用户的头像文件名
			RosterElementEntity localUserInfo = MyApplication.getInstance2().getIMClientManager().getLocalUserInfo();
			if(localUserInfo != null){
				avatarFileName = localUserInfo.getUserAvatarFileName();
			}
		}
		else {
			RosterElementEntity friendInfo = MyApplication.getInstance2().getIMClientManager().getFriendsListProvider().getFriendInfoByUid2(uid);
			// 此人是我的好友（不是陌生人）
			if(friendInfo !=  null){
				// 好友的话，因为能读取到对方的avatarFileName字段，当对方头像改变时avatarFileName也会改变，因为
				// loadAvatarImgWithGlide方法里已经据此使用了singnature签名，所以在此就不需要显示强制刷新了，即dontDiskCache无需为true
				avatarFileName = friendInfo.getUserAvatarFileName();
			}
			else{
				// 对于陌生人来说，extra1String中，存放的就是可能最新头像文件名（在查看最新用户资料时设置进来的）
				avatarFileName = MyApplication.getInstance2().getIMClientManager().getAlarmsProvider().getExtra1String(AlarmType.guestChat, uid);
				// 陌生人头像app启动后首次加载时不从碰盘读取，强制从网络读取一次，确保相同的url也能及时刷新头像显示缓存1次
				dontDiskCache = true;
			}
		}

		// 显示用户头像
		ImageCacheLoader.loadAvatarImgWithGlide(getContext(), uid, avatarFileName
				, viewHead, 20, R.drawable.default_avatar_for_chattingui_40_3x, dontDiskCache, false);
	}

    /**
     * 设置最后一行处于可见状态.
     */
    protected void selectionLastLine() {
        // 【消息列表最后一条可见时的处理2】
        // 如果最后一条一直是处于可见状态（通常是ListView是已被滑到了底部），则此时收到的消息就自动滚动到最新
        // ，否则用不自动滚动（在另外的未读消息处理机制里会有办法提示用户），因为有的时候用户想向上滚动看以前的消息
        // ，如果有消息就无条件向下滚动则用户想看之前的消息的体验就非常差（每差滑到以前后有新消息了又自动滑回来！）
        if(this.isLastItemVisible())
            // 默认最后一行处于可见状态
            showLastItem();
    }

    public void showLastItem() {
        // 默认最后一行处于可见状态
        theListView.setSelection(theListView.getCount() - 1);
    }

    /**
     * 列表的数据集合。
     * 子类实现本方法，从而使用自已的数据模型。
     *
     * @return
     */
	protected abstract ArrayListObservable<Message> getChattingDatas();

    /**
     * 消息重发实现方法。
     * 子类需实现本方法，从而实现消息的重发逻辑。
     *
     * @param entity
     */
    protected abstract void reSendImpl(Message entity);

	/**
	 * 点击实时音视频记录消息的实现方法。
	 * 子类可实现本方法，从而实现点击实时音视频记录触发音视频呼叫功能。
	 *
	 * @param vrm
	 * @since 10.0
	 */
	protected void onClickVoipRecordImpl(VoipRecordMeta vrm) {
		Log.w(TAG, "请子类实现本方法，默人什么也不做！");
	}

	/**
	 * 聊天界面中是否显示用户昵称。默认不显示。
	 * 子类可重写本方法，返回true即可显示昵称。
	 *
	 * @return true表示显示昵称，否则不显示
	 */
	protected  boolean isShowNickName()
	{
    	return false;
	}
	
	/**
	 * 父Activity销毁时即时释放相关资源。
	 */
	public void forParentDestraoy() {
		// 执行音频的release，释放资源
		voicePlayerWrapper.release();
	}

	/**
	 * 更新列表但不默认选中最后一行。
	 * <p>
	 * 本方法用于语音播放等场景，刷新ui的同时如果要显示最后一行那就不好罗。
	 */
	public void notifyDataSetChangedNotSelectLastLine() {
		super.notifyDataSetChanged();
	}

	/**
	 * 更新列表且默认显示列表最后一行。
	 * <p>
	 * 本方法通常用于聊天消息的显示时，以便随时显示最新消息。
	 */
	@Override
	public void notifyDataSetChanged() {
		super.notifyDataSetChanged();

		// 刷新列表显示后再自动设置最后一行可见，防止有些手机上layout里ListView的
		// “android:transcriptMode="alwaysScroll"”属性不起效（当然绝对多数情况下该属性能工作的很）
//		selectionLastLine();
	}

	
	//************************************************************************ 内部类 START
	
	/**
	 * 消息内容点击事件监听器.
	 */
	private class ContentOnClickListener implements View.OnClickListener {

		private int position = 0;
		
		/** 
		 * 在从服务端读取完图片、语音留言文件后服务端是否需要将此文件转储（用于减小存储压力）.
		 * 注：对于普通的一对一消息，是需要转储的，但BBS消息这样一对多的情况就不能转储，否则一个人
		 * 读取后其它人就再也读不到了（在服务端被转储了），未转储的文件交给服务端的定期清理线程
		 * 来定期清理就行了，所以理论上存储爆满问题也不会存在. */
		private boolean needDump = false;//true

		/**
		 * true表示点击的是文本消息下面的被"引用"消息内容，否则点击的是正常消息item。
		 */
		private boolean clickedTheQuote = false;
		
		public ContentOnClickListener(boolean clickedTheQuote, boolean needDump) {
			this.clickedTheQuote = clickedTheQuote;
			this.needDump = needDump;
		}

		public void setPosition(int position) {
			this.position = position;
		}

		@Override
		public void onClick(View v) {
			Message entity = AbstractChattingListAdapter.this.getItem(position);

			// 点击的是引用消息内容，且原消息已被撤回，则不能查看内容哦
			if(clickedTheQuote && entity.getQuote_status() == 1) {
				WidgetUtils.showWithDialog(context, context.getResources().getString(R.string.general_prompt), "原消息已被撤回，无法查看！");
				return;
			}

			int msgType = (clickedTheQuote ? entity.getQuote_type() : entity.getMsgType());

			if (msgType == MsgType.TYPE_IMAGE) {
				//** @since 10.0：本地发出的消息，有可能来自收到的消息的转发，转发的当然就不存在本地缓存的
				//**            （在此之前正常的发出图片消息是只读取本地缓存），所以也需要从网络加载这个逻辑
				// 图片消息时此字段存放的是图片的文件名
				String imageFileName = (clickedTheQuote ? entity.getQuote_content() : entity.getText());
				if (imageFileName != null) {
					File theCachedImageFile = new File(SendImageHelper.getSendPicSavedDirHasSlash() + imageFileName);
					if (theCachedImageFile.exists())
						context.startActivity(com.eva.android.IntentFactory.createImageViewActivityIntent_fromFile(context, theCachedImageFile.getAbsolutePath()));
					else
						context.startActivity(com.eva.android.IntentFactory.createImageViewActivityIntent_fromUrl(context, SendImageHelper.getImageDownloadURL(context, imageFileName, needDump), SendImageHelper.getSendPicSavedDir()));
				}
			}
			else if(msgType == MsgType.TYPE_VOICE){// 语言留言消息

				// 如果点击的是被引用的语音消息，则要找到原语音消息的对象，接下来的播放要在原对象上处理（这是参照微信的功能逻辑实现的！）
				Message beQuoteEntity = null;
				if(clickedTheQuote) {
					if (theChatType == ChatType.CHAT_TYPE_FREIDN$CHAT || theChatType == ChatType.CHAT_TYPE_GUEST$CHAT) {
						beQuoteEntity = MyApplication.getInstance2().getIMClientManager()
								.getMessagesProvider().findMessageByFingerPrint(theToId, entity.getQuote_fp());
					} else if (theChatType == ChatType.CHAT_TYPE_GROUP$CHAT) {
						beQuoteEntity = MyApplication.getInstance2().getIMClientManager()
								.getGroupsMessagesProvider().findMessageByParentFingerPrint(theToId, entity.getQuote_fp());
					}

					if(beQuoteEntity == null) {
						WidgetUtils.showWithDialog(context, context.getResources().getString(R.string.general_prompt), "原语音消息内容已不存在（可能被删除），无法查看！");
						return;
					}
				}

				// 如果正在点击的单元正处于”播放中“则此次点击将停止当前正在进行中的播放
				if(voicePlayerWrapper.isEntityVoicePlaying(clickedTheQuote ? beQuoteEntity : entity)) {
					voicePlayerWrapper.stopVoice();
					return;
				}
				
				// 点击此单元就意味着此语音留言处于“播放中“状态（因为一次只能播放一个，所以可以作为全局变量存起来
				// ，只要ListView的单元对应的是此currentVoicePlayingCME就意味着它正处于”播放中“状态哦）
				voicePlayerWrapper.setPlayingStatus(clickedTheQuote ? beQuoteEntity : entity, true);
				// 播放声音
				boolean playSucess = voicePlayerWrapper.playVoice(clickedTheQuote ? beQuoteEntity : entity, needDump);
				// 如果播放成功进行了
				if(playSucess)
					;
				else {
					// 播放失败则清空播放状态
					voicePlayerWrapper.clearPlayingStatus(true);
				}
				
//				// 刷新ui以便即进更新播放状态显示
//				AbstractChattingListAdapter.this.notifyDataSetChanged();
			}
			// 大文件消息
			else if(msgType == MsgType.TYPE_FILE){// 文件消息
				// 文件类型的消息体存放的是FileMeta对象转JSON后的文本
				FileMeta fm = (FileMeta)(clickedTheQuote ? entity.getQuoteContentObject() : entity.getTextObject());
				if(fm != null) {
					try {
						// 如果是发出的消息则entity中原始存放的是本地绝对路径（含文件名），否则只是文件名
						String fileName = fm.getFileName();
						String fileMd5 = fm.getFileMd5();
						long fileLen = fm.getFileLength();
						String filePath = fm.getFilePath();

						//# Bug Fix 240329-andriod1
						// 做一下检查，如果是别处传过来未设置，就在此处补救一下，目前主要是ios端发过来的引用文件消息时
						if(CommonUtils.isStringEmpty(filePath, true)) {
							filePath = ReceivedFileHelper.getReceivedFileSavedDirHasSlash() + fm.getFileName();
						}

						// v10.0开始，因支持消息转发功能，所以此条"发出的"文件消息，可能来自于接收到的文件消息，
						// 而此时接收到的文件消息并未完成文件下载，则此条因转发而"发出的"消息，显示不能跟正常发
						// 出的文件消息一样的逻辑。于是，从v10.0开始，为了解决因转发而"发出的"的文件消息，所以
						// 无条件设置canDownload为true，也就是允许因转发而"发出的"的文件消息在查看时也能下载
//						boolean canDownload = (isOutgoing?false:true);
						boolean canDownload = true;

						Log.w(TAG, "getQuote_content="+entity.getQuote_content()+", filePath="+filePath);

						File f = new File(filePath);
						// 取出文件所在的目录
						String fileDir = f.getParentFile().getAbsolutePath();

						Intent it = IntentFactory.createBigFileViewerIntent(context, fileName, fileDir, fileMd5, fileLen, canDownload);
						context.startActivity(it);
					} catch(Exception e) {
						Log.w(TAG, e);
					}
				}
			}
            // 短视频消息
            else if(msgType == MsgType.TYPE_SHORTVIDEO){// 短视频消息
                // 文件类型的消息体存放的是FileMeta对象转JSON后的文本
                FileMeta fm = (FileMeta)(clickedTheQuote ? entity.getQuoteContentObject() : entity.getTextObject());
                if(fm != null) {
                    try {
                        // 如果是发出的消息则entity中原始存放的是本地绝对路径（含文件名），否则只是文件名
                        String fileName = fm.getFileName();
                        String fileMd5 = fm.getFileMd5();
                        long fileLen = fm.getFileLength();
                        String filePath = fm.getFilePath();

						//# Bug Fix 240329-andriod1
						// 做一下检查，如果是别处传过来未设置，就在此处补救一下，目前主要是ios端发过来的引用文件消息时
						if(CommonUtils.isStringEmpty(filePath, true)) {
							filePath = ReceivedShortVideoHelper.getReceivedShortVideoSavedDirHasSlash() + fm.getFileName();
						}

                        File f = new File(filePath);
                        // 取出文件所在的目录
                        String fileDir = f.getParentFile().getAbsolutePath();

                        if(f.exists() && f.length() == fileLen) {
                            Log.d(TAG, "点击的视频文件："+fileName+"已经存在，直接播放之！");
                            context.startActivity(com.eva.android.IntentFactory.createShortVideoPlayerActivityIntent_fromFile(context, filePath));
                        } else {
                            Log.d(TAG, "点击的视频文件："+fileName+"不存在或不完整，播放前可能需要下载哦。。。");
                            Intent i = com.eva.android.IntentFactory.createShortVideoPlayerActivityIntent_fromUrl(context, ReceivedShortVideoHelper.getShortVideoDownloadURL(context, fileName, fileMd5), fileDir);
                            context.startActivity(i);
                        }
                    } catch(Exception e) {
                        Log.w(TAG, e);
                    }
                }
            }
            // 个人名片消息
            else if(msgType == MsgType.TYPE_CONTACT) {
				// 文件类型的消息体存放的是FileMeta对象转JSON后的文本
				ContactMeta cm = (ContactMeta)(clickedTheQuote ? entity.getQuoteContentObject() : entity.getTextObject());
				if(cm != null) {
					if(cm.getType() == ContactMeta.TYPE_USER) {
						// 进入用户信息查看界面
						QueryFriendInfo.gotoWatchUserInfo((Activity) context, cm.getUid(), null);
					} else {
						String value = QRCodeScheme.constructJoinGroupCodeSubStr(cm.getUid(), entity.getSenderId());
						// 进入加群界面
						context.startActivity(IntentFactory.createJoinGroupIntent(context, value, JoinGroupActivity.JOIN_BY_GROUP_CONTACT));
					}
				}
			}
			// 发出的和收到的位置消息
			else if(msgType == MsgType.TYPE_LOCATION) {
				// 位置消息类型的消息体存放的是LocationMeta对象转JSON后的文本
				LocationMeta lm = (LocationMeta)(clickedTheQuote ? entity.getQuoteContentObject() : entity.getTextObject());
				if(lm != null) {
					context.startActivity(IntentFactory.createViewLocationActivityIntent(context, lm));
				}
			}
			// 发出和收到的实时音视频记录消息
			else if(msgType == MsgType.TYPE_VOIP$RECORD) {
				// 消息体存放的是VoipRecordMeta对象转JSON后的文本
				VoipRecordMeta vrm = (VoipRecordMeta)(clickedTheQuote ? entity.getQuoteContentObject() : entity.getTextObject());
				if(vrm != null) {
					onClickVoipRecordImpl(vrm);
				}
			}
			else {
				// 如果被引用的消息是文字消息，则弹个框显示原消息内容
				if(clickedTheQuote && msgType == MsgType.TYPE_TEXT && !CommonUtils.isStringEmpty(entity.getQuote_content())) {
					// FFF emoji图片表情转换
					SpannableString ss = EmojiUtil.replaceEmoticons(context, entity.getQuote_content(), 50, 50, DynamicDrawableSpan.ALIGN_BOTTOM);
					WidgetUtils.showWithDialog(context, "被引用的消息内容", ss);
				}
//				WidgetUtils.showToast(FriendChattingActivity.this, "Temporarily do not have this function.");

			}
		}
	}
	
	/**
	 * 用户头像点击事件监听器.
	 */
	private class HeadIconOnClickListener implements View.OnClickListener {
		private int position = 0;

		public void setPosition(int position)
		{
			this.position = position;
		}

		@Override
		public void onClick(View v) {
			Message entity = AbstractChattingListAdapter.this.getItem(position);
			// 发出的消息
            if(entity.isOutgoing()) {
				// 点击查看本地用户的个人信息
				Intent intent = new Intent(context, UserActivity.class);
				context.startActivity(intent);

			}
			// 收到的消息
			else {
				// 点击则查看该消息发送人的个人信息
//				new QueryFriendInfo((Activity)context).execute(false, null, entity.getSenderId());
				QueryFriendInfo.gotoWatchUserInfo((Activity)context, entity.getSenderId(), null);
			}
		}
	}

    /**
     * 消息发送失败后的失败提示图标点击事件监听器.
     */
    private class SendFailedImageOnClickListener implements View.OnClickListener {

        private int position = 0;

        public void setPosition(int position) {
            this.position = position;
        }

        @Override
        public void onClick(View v) {
            final Message entity = AbstractChattingListAdapter.this.getItem(position);
            if(entity != null) {
                // 发出的消息
                if (entity.isOutgoing()) {
                    new AlertDialog.Builder(context)
                            .setTitle(context.getResources().getString(R.string.chat_resend_prompt_title))
                            .setMessage(context.getResources().getString(R.string.chat_resend_prompt_msg))
                            .setPositiveButton(context.getResources().getString(R.string.general_ok), (dialog, which) -> reSendImpl(entity))
                            .setNegativeButton(context.getResources().getString(R.string.general_cancel), null)
                            .show();
                }
            }
            else {
                Log.w(TAG, "entity是null，消息重发不能继续哦。");
            }
        }
    }

	//************************************************************************ 内部类 END
}